// $Id: ParameterBuilder.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Utility classes that can be used to generate command parameters.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Security.Permissions;
using System.Text.RegularExpressions;

namespace Salamanca.DataAccess.Data
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Utility class used to generate command parameters.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class ParameterBuilder
    {

        /// <summary>Constructor.</summary>
        public ParameterBuilder(DbConnection connection)
        {
            Debug.Assert(connection!=null);
            if (connection==null)
                throw new ArgumentNullException("connection");

            _Connection=connection;
            _Connection.Disposed+=new EventHandler(ConnectionDisposed);
        }

        private void ConnectionDisposed(object sender, EventArgs e)
        {
            _Schemas.Remove(_Connection);
            _CommandBuilders.Remove(_Connection);
        }

        /// <summary>Returns the marker for the specified name.</summary>
        /// <param name="name">The name to derive the marker from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@name"</c>.</para>
        ///     <para>For Oracle, this will return <c>":name"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetMarker(string name)
        {
            name=GetName(name);
            return string.Format(
                CultureInfo.InvariantCulture,
                (string)Schema[DbMetaDataColumnNames.ParameterMarkerFormat],
                name.Substring(0, Math.Min(name.Length, (int)Schema[DbMetaDataColumnNames.ParameterNameMaxLength]))
            );
        }

        /// <summary>Returns the marker for the specified ordinal.</summary>
        /// <param name="parameterOrdinal">The ordinal to derive the marker from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@1"</c>.</para>
        ///     <para>For Oracle, this will return <c>":1"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetMarker(int parameterOrdinal)
        {
            // Hack to make sure we have the right token...
            return (string)typeof(DbCommandBuilder).InvokeMember(
                "GetParameterPlaceholder",
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
                null,
                CommandBuilder,
                new object[] { parameterOrdinal }
#if (!NET_CF)
                , null
#endif
            );
        }

        /// <summary>Returns the marker for the specified name and ordinal.</summary>
        /// <param name="name">The name to derive the marker from.</param>
        /// <param name="parameterOrdinal">The ordinal to derive the marker from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@1"</c>.</para>
        ///     <para>For Oracle, this will return <c>":1"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetMarker(string name, int parameterOrdinal)
        {
            return GetMarker(parameterOrdinal);
        }

        /// <summary>Returns the parameter name for the specified name.</summary>
        /// <param name="name">The name to derive the parameter name from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@name"</c>.</para>
        ///     <para>For Oracle, this will return <c>"name"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetName(string name)
        {
            if ((((string)Schema[DbMetaDataColumnNames.DataSourceProductName])=="Oracle") && (CommandBuilder.GetType().Namespace=="Oracle.DataAccess.Client"))
                // Oracle will never have it the proper way (cf. http://forums.oracle.com/forums/thread.jspa?forumID=146&threadID=423300)...
                return name;
            else
                // Hack to make sure we have the right token...
                return (string)typeof(DbCommandBuilder).InvokeMember(
                    "GetParameterName",
                    BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
                    null,
                    CommandBuilder,
                    new object[] { name }
#if (!NET_CF)
                    ,null
#endif
                );
        }

        /// <summary>Returns the parameter name for the specified ordinal.</summary>
        /// <param name="parameterOrdinal">The ordinal to derive the parameter name from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@1"</c>.</para>
        ///     <para>For Oracle, this will return <c>":1"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetName(int parameterOrdinal)
        {
            // Hack to make sure we have the right token...
            return (string)typeof(DbCommandBuilder).InvokeMember(
                "GetParameterName",
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
                null,
                CommandBuilder,
                new object[] { parameterOrdinal }
#if (!NET_CF)
                , null
#endif
            );
        }

        /// <summary>Returns the parameter name for the specified name and ordinal.</summary>
        /// <param name="name">The name to derive the parameter name from.</param>
        /// <param name="parameterOrdinal">The ordinal to derive the parameter name from.</param>
        /// <remarks>
        ///   <example>
        ///     <para>For SQL Server, this will return <c>"@1"</c>.</para>
        ///     <para>For Oracle, this will return <c>":1"</c>.</para>
        ///   </example>
        /// </remarks>
        public string GetName(string name, int parameterOrdinal)
        {
            return GetName(parameterOrdinal);
        }

        private DataRow Schema
        {
            get
            {
                if (_Schema==null)
                {
                    if (!_Schemas.TryGetValue(_Connection, out _Schema))
                        using (DbConnectionAdapter c=new DbConnectionAdapter(_Connection))
                        {
                            c.Open();

                            _Schema=_Connection.GetSchema(DbMetaDataCollectionNames.DataSourceInformation).Rows[0];
                            _Schemas[_Connection]=_Schema;
                        }
                }

                return _Schema;
            }
        }

        private DbCommandBuilder CommandBuilder
        {
            get
            {
                if ((_CommandBuilder==null) && (!_Initialized))
                {
                    if (!_CommandBuilders.TryGetValue(_Connection, out _CommandBuilder))
                    {
#if (!NET_CF)
                        try
                        {
                            DbProviderFactory factory=DbProviderFactories.GetFactory(_Connection.GetType().Namespace);
                            if (factory!=null)
                            {
                                _CommandBuilder=factory.CreateCommandBuilder();
                                _CommandBuilders[_Connection]=_CommandBuilder;
                            }
                        } catch (ArgumentException ex)
                        {
                            if (Configuration.Switch.TraceWarning)
                            {
                                Trace.WriteLine(
                                    ex.Message,
                                    Traces.GetCategory(Configuration.Switch, TraceLevel.Warning)
                                );
                            }
                        }
#else
                        // We have to implement our own DbProviderFactory...
                        string cbTypeName=string.Empty;
                        switch ((string)Schema[DbMetaDataColumnNames.DataSourceProductName])
                        {
                        case "SqlServerCe":
                            cbTypeName="System.Data.SqlServerCe.SqlCeCommandBuilder";
                            break;
                        default:
                            // Give it a try...
                            cbTypeName=string.Format(CultureInfo.InvariantCulture, "System.Data.{0}CommandBuilder", Schema[DbMetaDataColumnNames.DataSourceProductName]);
                            break;
                        }
                        _CommandBuilder=(DbCommandBuilder)Type.GetType(cbTypeName).GetConstructor(new Type[] { }).Invoke(new object[] { });
                        _CommandBuilders[_Connection]=_CommandBuilder;
#endif
                        _Initialized=true;
                    }
                }

                return _CommandBuilder;
            }
        }

        private DbConnection _Connection;
        private DataRow _Schema;
        private DbCommandBuilder _CommandBuilder;
        private bool _Initialized;

        private static Dictionary<DbConnection, DataRow> _Schemas=new Dictionary<DbConnection, DataRow>();
        private static Dictionary<DbConnection, DbCommandBuilder> _CommandBuilders=new Dictionary<DbConnection, DbCommandBuilder>();
    }
}
